package com.sjs.flow.service.impl;

import com.sjs.flow.service.ImageService;
import com.sjs.flow.utils.CustomProcessDiagramGenerator;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.bpmn.model.SequenceFlow;
import org.flowable.engine.HistoryService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.Execution;
import org.flowable.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author sjs
 * @desc
 * @since 2021/12/16 9:46
 */
@Service
public class ImageServiceImpl implements ImageService {

    @Autowired
    HistoryService historyService;

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    RepositoryService repositoryService;

    /**
     * 根据流程实例Id,获取实时流程图片
     *
     * @param procInstId
     * @return
     * @throws Exception
     */
    @Override
    public BufferedImage generateImageByProcInstId(String procInstId) throws Exception {
        InputStream imageStream = null;
        try {
            // 通过流程实例ID获取历史流程实例
            HistoricProcessInstance historicProcessInstance = getHistoricProcInst(procInstId);

            // 通过流程实例ID获取流程中已经执行的节点，按照执行先后顺序排序
            List<HistoricActivityInstance> historicActivityInstanceList = getHistoricActivityInstAsc(procInstId);


            // 将已经执行的节点ID放入高亮显示节点集合
            List<String> highLightedActivitiIdList = new ArrayList<>();
            for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
                highLightedActivitiIdList.add(historicActivityInstance.getActivityId());
            }

            // 通过流程实例ID获取流程中正在执行的节点
            List<Execution> runningActivityInstanceList = getRunningActivityInst(procInstId);
            List<String> runningActivitiIdList = new ArrayList<String>();
            for (Execution execution : runningActivityInstanceList) {
                if (!StringUtils.isEmpty(execution.getActivityId())) {
                    runningActivitiIdList.add(execution.getActivityId());
                }
            }

            // 通过流程实例ID获取已经完成的历史流程实例
            List<HistoricProcessInstance> historicFinishedProcessInstanceList = getHistoricFinishedProcInst(procInstId);

            // 定义流程画布生成器
            ProcessDiagramGenerator processDiagramGenerator = null;

            processDiagramGenerator = new CustomProcessDiagramGenerator();

            // 获取流程定义Model对象
            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());

            // 获取已流经的流程线，需要高亮显示高亮流程已发生流转的线id集合
            List<String> highLightedFlowIds = getHighLightedFlows(bpmnModel, historicActivityInstanceList);

            // 使用默认配置获得流程图表生成器，并生成追踪图片字符流
            imageStream = ((CustomProcessDiagramGenerator) processDiagramGenerator)
                    .generateDiagramCustom(bpmnModel, "png",
                            highLightedActivitiIdList, runningActivitiIdList, highLightedFlowIds,
                            "宋体", "宋体", "宋体",
                            null, 1.0);
            // 将InputStream数据流转换为byte[]
            BufferedImage bufferedImage = ImageIO.read(imageStream);
            return bufferedImage;
        } finally {
            if (imageStream != null) {
                imageStream.close();
            }
        }
    }

    /**
     * 根据流程实例Id,获取实时流程跟踪
     *
     * @param procInstId
     * @return
     * @throws Exception
     */
    @Override
    public List<Map<String, Object>> generateStepByProcInstId(String procInstId) {
//        Map<String,String> mapParam=new HashMap<>();
//        DefaultResponse defaultResponse=authFeignClient.listAllUser(mapParam);
//        List<XSUsers> list=(List<XSUsers>) defaultResponse.getData();
//        Map<String,XSUsers> mapUser=list.stream().collect(Collectors.toMap(XSUsers::getId, user -> user));
//
//        List<VHashMap<String,Object>> listTracking=processTrackingMapper.processTracking(procInstId);
//
//        listTracking.forEach((item) -> {
//            String assignee=item.get("assignee")==null ? "" :item.get("assignee").toString();
//            XSUsers xsUsers=null;
//            if(!StringUtils.isEmpty(assignee)){
//                xsUsers = mapUser.get(assignee);
//                assignee = xsUsers.getUserRealname();
//            }else{
//                assignee="";
//            }
//            item.put("assignee",assignee);
//            item.put("phone",xsUsers.getUserPhone());
//            item.put("email",xsUsers.getUserEmail());
//            TIMESTAMP ts1=(TIMESTAMP) item.get("starttime");
//            TIMESTAMP ts2=(TIMESTAMP) item.get("endtime");
//            TIMESTAMP ts3=(TIMESTAMP) item.get("processstarttime");
//            TIMESTAMP ts4=(TIMESTAMP) item.get("processendtime");
//            try {
//                item.put("starttime",XSStringUtil.formatTime(new Date(ts1.dateValue().getTime())));
//                item.put("endtime",XSStringUtil.formatTime(new Date(ts2.dateValue().getTime())));
//                item.put("processstarttime",XSStringUtil.formatTime(new Date(ts3.dateValue().getTime())));
//                if(ts4!=null){
//                    item.put("processendtime",XSStringUtil.formatTime(new Date(ts4.dateValue().getTime())));
//                }else{
//                    item.put("processendtime","");
//                }
//
//            } catch (SQLException throwables) {
//                throwables.printStackTrace();
//            }
//            item.put("currenttask",0);
//            item.put("message",item.get("message"));
//        });
//        List<VHashMap<String,Object>> listCurrent=processTrackingMapper.getCurrentTask(procInstId);
//        VHashMap<String,Object> mapCurrent=null;
//        Boolean reject=false;
//        if(listCurrent.size()>0){
//            mapCurrent=listCurrent.get(0);
//            BpmnModel bpmnModel=repositoryService.getBpmnModel(mapCurrent.get("defid").toString());
//            UserTask userTask = (UserTask)bpmnModel.getFlowElement(mapCurrent.get("key").toString());
//            List<String> roles = userTask.getCandidateGroups();
//            DefaultResponse<List<Xsrole>> defaultResponse1 = authFeignClient.getRoles(roles);
//            List<Xsrole> listrole = (List<Xsrole>)defaultResponse1.getData();
//            List<String> role = listrole.stream().map((p)->{
//                return p.getXsroleName();
//            }).collect(Collectors.toList());
//            mapCurrent.put("roles", Joiner.on(" ,").join(role));
//            String condi =  userTask.getLoopCharacteristics().getCompletionCondition().toLowerCase();
//            if(condi.contains("true")){
//                mapCurrent.put("jointly",true);
//            }else{
//                mapCurrent.put("jointly",false);
//            }
//            mapCurrent.put("currenttask",1);
//            TIMESTAMP ts=(TIMESTAMP) mapCurrent.get("processstarttime");
//            try {
//                mapCurrent.put("processstarttime",XSStringUtil.formatTime(new Date(ts.dateValue().getTime())));
//            } catch (SQLException throwables) {
//                throwables.printStackTrace();
//            }
//            listTracking.add(mapCurrent);
//            List<XSUsers> listmp=listCurrent.stream().map((item) -> {
//                XSUsers xsUsers=mapUser.get(item.get("assignee").toString());
//                return xsUsers;
//            }).collect(Collectors.toList());
//            mapCurrent.put("assignee",listmp);
//        }else{
//            List<String> list1=processTrackingMapper.isreject(procInstId);
//            VHashMap<String,Object> map=new VHashMap<>();
//            if(list1.size()>0){
//                map.put("name","流程否决");
//                map.put("reject",true);
//            }else{
//                map.put("name","流程通过");
//                map.put("reject",false);
//            }
//            String endtime=listTracking.get(0).get("processendtime").toString();
//            map.put("endtime",endtime);
//            listTracking.add(map);
//        }
//
//        //发起人、开始节点
//        String userid=listTracking.get(0).get("startuser").toString();
//        VHashMap<String,Object> map=new VHashMap<>();
//        map.put("startuser",mapUser.get(userid).getUserRealname());
//        map.put("phone",mapUser.get(userid).getUserPhone());
//        map.put("email",mapUser.get(userid).getUserEmail());
//        map.put("starttime",listTracking.get(0).get("processstarttime"));
//        String bussId=listTracking.get(0).get("businesskey").toString();
//        if(bussId.split("&").length>0){
//            bussId=bussId.split("&")[0];
//        }else{
//            bussId="";
//        }
//
//        map.put("bussid",bussId);
//        listTracking.add(0,map);
//        return listTracking;
        return null;
    }

    /**
     * 通过流程实例ID获取历史流程实例
     *
     * @param procInstId
     * @return
     */
    public HistoricProcessInstance getHistoricProcInst(String procInstId) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).singleResult();
    }


    /**
     * 通过流程实例ID获取流程中已经执行的节点，按照执行先后顺序排序
     *
     * @param procInstId
     * @return
     */
    public List<HistoricActivityInstance> getHistoricActivityInstAsc(String procInstId) {
        return historyService.createHistoricActivityInstanceQuery().processInstanceId(procInstId)
                .orderByHistoricActivityInstanceId().asc().list();
    }

    /**
     * 通过流程实例ID获取流程中正在执行的节点
     *
     * @param procInstId
     * @return
     */
    public List<Execution> getRunningActivityInst(String procInstId) {
        return runtimeService.createExecutionQuery().processInstanceId(procInstId).list();
    }

    /**
     * 通过流程实例ID获取已经完成的历史流程实例
     *
     * @param procInstId
     * @return
     */
    public List<HistoricProcessInstance> getHistoricFinishedProcInst(String procInstId) {
        return historyService.createHistoricProcessInstanceQuery().processInstanceId(procInstId).finished().list();
    }

    /**
     * 获取已流经的流程线，需要高亮显示高亮流程已发生流转的线id集合
     *
     * @param bpmnModel
     * @param historicActivityInstanceList
     * @return
     */
    public List<String> getHighLightedFlows(BpmnModel bpmnModel,
                                            List<HistoricActivityInstance> historicActivityInstanceList) {
        // 已流经的流程线，需要高亮显示
        List<String> highLightedFlowIdList = new ArrayList<>();
        // 全部活动节点
        List<FlowNode> allHistoricActivityNodeList = new ArrayList<>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstanceList = new ArrayList<>();

        for (HistoricActivityInstance historicActivityInstance : historicActivityInstanceList) {
            // 获取流程节点
            if (historicActivityInstance.getActivityType().equals("sequenceFlow")) {
                continue;
            }

            FlowElement fe = bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true);

            FlowNode flowNode = (FlowNode) fe;
            allHistoricActivityNodeList.add(flowNode);
            // 结束时间不为空，当前节点则已经完成
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstanceList.add(historicActivityInstance);
            }
        }

        FlowNode currentFlowNode = null;
        FlowNode targetFlowNode = null;
        HistoricActivityInstance currentActivityInstance;
        // 遍历已完成的活动实例，从每个实例的outgoingFlows中找到已执行的
        for (int k = 0; k < finishedActivityInstanceList.size(); k++) {
            currentActivityInstance = finishedActivityInstanceList.get(k);
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance
                    .getActivityId(), true);
            // 当前节点的所有流出线
            List<SequenceFlow> outgoingFlowList = currentFlowNode.getOutgoingFlows();

            /**
             * 遍历outgoingFlows并找到已流转的 满足如下条件认为已流转：
             * 1.当前节点是并行网关或兼容网关，则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
             * 2.当前节点是以上两种类型之外的，通过outgoingFlows查找到的时间最早的流转节点视为有效流转
             * (第2点有问题，有过驳回的，会只绘制驳回的流程线，通过走向下一级的流程线没有高亮显示)
             */
            if ("parallelGateway".equals(currentActivityInstance.getActivityType()) || "inclusiveGateway".equals(
                    currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点，找到匹配流程目标节点的
                for (SequenceFlow outgoingFlow : outgoingFlowList) {
                    // 获取当前节点流程线对应的下级节点
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(outgoingFlow.getTargetRef(),
                            true);
                    // 如果下级节点包含在所有历史节点中，则将当前节点的流出线高亮显示
                    if (allHistoricActivityNodeList.contains(targetFlowNode)) {
                        highLightedFlowIdList.add(outgoingFlow.getId());
                    }
                }
            } else {
                /**
                 * 2、当前节点不是并行网关或兼容网关
                 * 【已解决-问题】如果当前节点有驳回功能，驳回到申请节点，
                 * 则因为申请节点在历史节点中，导致当前节点驳回到申请节点的流程线被高亮显示，但实际并没有进行驳回操作
                 */
                List<Map<String, Object>> tempMapList = new ArrayList<>();
                // 当前节点ID
                String currentActivityId = currentActivityInstance.getActivityId();
                int size = historicActivityInstanceList.size();
                boolean ifStartFind = false;
                boolean ifFinded = false;
                HistoricActivityInstance historicActivityInstance;
                // 循环当前节点的所有流出线
                for (int i = 0; i < historicActivityInstanceList.size(); i++) {
                    // 历史节点
                    historicActivityInstance = historicActivityInstanceList.get(i);
                    // 如果循环历史节点中的id等于当前节点id，从当前历史节点继续先后查找是否有当前节点流程线等于的节点
                    // 历史节点的序号需要大于等于已完成历史节点的序号，防止驳回重审一个节点经过两次是只取第一次的流出线高亮显示，第二次的不显示
                    if (i >= k && historicActivityInstance.getActivityId().equals(currentActivityId)) {
                        ifStartFind = true;
                        // 跳过当前节点继续查找下一个节点
                        continue;
                    }
                    if (ifStartFind) {
                        ifFinded = false;
                        for (SequenceFlow sequenceFlow : outgoingFlowList) {
                            // 如果当前节点流程线对应的下级节点在其后面的历史节点中，则该条流程线进行高亮显示
                            if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                                highLightedFlowIdList.add(sequenceFlow.getId());
                                // 暂时默认找到离当前节点最近的下一级节点即退出循环，否则有多条流出线时将全部被高亮显示
                                ifFinded = true;
                                break;
                            }
                        }
                    }
                    if (ifFinded) {
                        // 暂时默认找到离当前节点最近的下一级节点即退出历史节点循环，否则有多条流出线时将全部被高亮显示
                        break;
                    }
                }
            }

        }
        return highLightedFlowIdList;
    }


}
